package net.sf.xoperator.xmpp;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import net.sf.xoperator.Constants;
import net.sf.xoperator.blackboard.ConfigurationAwareKnowledgeSource;
import net.sf.xoperator.blackboard.NoPlanBlackBoardControl;
import net.sf.xoperator.configuration.KSConfiguration;
import net.sf.xoperator.rdfxmpp.SPARQLQuery;
import net.sf.xoperator.rdfxmpp.SPARQLResult;
import net.sf.xoperator.ui.MessageToUser;
import net.sf.xoperator.ui.StringMessage;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.openbbs.blackboard.filter.KindOfFilter;
import org.openbbs.blackboard.ks.KSExecutionContext;

/**
 * Service class for all the xmpp related stuff. Logs into the network, creates
 * accounts when neccesary and posts and reads messages from the blackboard.
 * 
 * @author joerg
 * 
 */
public class XMPPManager extends ConfigurationAwareKnowledgeSource {

	private SPARQLResult.IQPacketListener mainClientResultListener;

	private Log log = LogFactory.getLog(XMPPManager.class);

	private NoPlanBlackBoardControl control;

	/**
	 * the account of the user
	 */
	private XmppClient mainClient;

	/**
	 * the account used for interaction
	 */
	private XmppClient proxyClient;

	@Override
	public void configure(KSConfiguration config) throws Exception {
		AccountInfo info = (AccountInfo) config;

		if (info.isMainAccount()) {
			logIntoMain(info);
		} else {
			logIntoProxy(info);
		}

		// makes sure the proxy and the main account know each other

		if (mainClient != null && proxyClient != null) {
			// check if main knows proxy
			introduce(mainClient, proxyClient);


		}

	}

	/**
	 * introduces client1 to client2
	 * 
	 * @return true if previusly unknown
	 */
	private void introduce(XmppClient client1, XmppClient client2) {
		client1.allowRosterAccess(client2.accountInfo.getUsername(), "My Agent");
		client2.allowRosterAccess(client1.accountInfo.getUsername(), "My Master");
	}

	private void logIntoMain(AccountInfo info) {
		if (this.mainClient != null) {
			this.mainClient.logoff();
		}
		this.mainClient = new XmppClient(info);
		try {
			this.mainClient.login();
		} catch (XMPPException e) {
			this.log.error(e);
			throw new RuntimeException(e);
		}

		if (info.isP2penabled()) {
			registerInterAgentCommunicationListener(mainClient);
		}
		if(info.isStandalone()){
			registerChatListeners(mainClient);
		}

	}

	private void logIntoProxy(AccountInfo info) {
		if (this.proxyClient != null) {
			this.proxyClient.logoff();
		}
		this.proxyClient = new XmppClient(info);

		try {
			this.proxyClient.login();
		} catch (XMPPException e) {
			this.log.info("No valid inforamtion "
					+ "to log into proxy account, trying to create one", e);
			try {
				this.proxyClient
						.createAccount(this.mainClient.getAccountInfo());
			} catch (XMPPException e1) {
				this.log.error("Unable to create proxy account", e);
				throw new RuntimeException(e);
			}

		}
		if (this.proxyClient.isLoggedIn()) {
			registerChatListeners(this.proxyClient);
		}

	}

	/**
	 * return of the answered queries
	 */
	@Override
	public void executeInternal(KSExecutionContext context) {

		sendPendingMessagesToUser(context);

		// forwardQueriesAndResults(context);

	}

	private void forwardQueriesAndResults(KSExecutionContext context) {
		// check if we got a SPARQL query, that has a non P2P output type,
		// we will forward it to all clients, if the agent list is null,
		// forward it to non if the agent list is empty and forward it to the
		// specified, if the list contains vald and knwon jids.
		SPARQLQuery query = (SPARQLQuery) context.blackboard().read(
				new KindOfFilter(SPARQLQuery.class));
		if (query != null && !query.getOutputType().equals(SPARQLQuery.P2P)
				&& query.getHtl() > 0) {
			List<String> jids = mainClient.getSPARQLEnabledClients();
			if (query.getAgentsToAsk() == null) {
				; // nothin to do, the list is prefect like that
			} else if (query.getAgentsToAsk().isEmpty()) {
				jids.clear();
			} else {
				List<String> tmpjids = new ArrayList<String>();
				for (String jid : jids) {
					for (String agentToAsk : query.getAgentsToAsk()) {
						if (jid.startsWith(agentToAsk)) {
							tmpjids.add(jid);
						}
					}
				}
				jids = tmpjids;
			}
			String agents = "";
			for (String jid : jids) {
				agents += agents + " ";
				query.setTo(jid);
				mainClient.sendQuery(query);
				mainClientResultListener.registerQuery(query, context);

			}
			query.setHtl(0);
			context.blackboard().write(
					new StringMessage("Passed the query to stores: ") + agents);
		}

		if (query != null && query.getOutputType().equals(SPARQLQuery.P2P)) {
			for (Object object : context.blackboard().readAll(
					new KindOfFilter(SPARQLResult.class))) {
				SPARQLResult result = (SPARQLResult) object;
				if (!result.isSent()) {
					mainClient.respondToQuery(query, result);
				}

			}
		}

	}

	private void sendPendingMessagesToUser(KSExecutionContext context) {
		Chat chat = (Chat) context.blackboard().read(
				new KindOfFilter(Chat.class));

		if (chat != null) {
			Set messages = context.blackboard().readAll(
					new KindOfFilter(MessageToUser.class));
			Set sendMsgNote = context.blackboard().readAll(
					new KindOfFilter(MessageSend.class));
			if (messages.size() > sendMsgNote.size()) {
				for (Object msgObj : messages) {
					MessageToUser message = (MessageToUser) msgObj;
					boolean sent = false;
					for (Object smoObj : sendMsgNote) {
						MessageSend msgsend = (MessageSend) smoObj;
						if (msgsend.getMessage().equals(message)) {
							sent = true;
						}
					}

					if (!sent) {
						try {
							chat.sendMessage(message.getMessage());
							MessageSend sendnote = new MessageSend();
							sendnote.setMessage(message);
							context.blackboard().write(sendnote);
						} catch (XMPPException e) {
							this.log.error(Constants.ERR_COM_001, e);
						}
					}

				}
			}
		}

	}

	@Override
	public boolean isEnabledInternal(KSExecutionContext context) {
		// for return messages
		return (context.blackboard().exists(
				new KindOfFilter(MessageToUser.class))
				|| context.blackboard().exists(
						new KindOfFilter(SPARQLQuery.class)) || context
				.blackboard().exists(new KindOfFilter(SPARQLResult.class)));
		// ||context.blackboard().exists(new KindOfFilter(String.class)))
		// && !context.blackboard().exists(new KindOfFilter(MessageSend.class));
	}

	public XMPPManager(NoPlanBlackBoardControl control) {
		super();
		this.control = control;
	}

	private void registerChatListeners(XmppClient client) {
		ChatManagerListener listener = new ChatManagerListener() {

			public void chatCreated(Chat chat, boolean arg1) {
				chat.addMessageListener(new MessageListener() {

					public void processMessage(Chat chat, Message message) {

						XMPPManager.this.control.registerMessage(message, chat);
					}
				});
			}
		};
		client.registerChatListener(listener);
	}

	private void registerInterAgentCommunicationListener(XmppClient client) {
		this.mainClientResultListener = new SPARQLResult.IQPacketListener();
		client.setSPARQLEnabled(true, new SPARQLQuery.IQPaketListener(control),
				new SPARQLQuery.IQPacketFilter(), mainClientResultListener,
				new SPARQLResult.IQPacketFilter());

	}

	@Override
	public List<Class<? extends KSConfiguration>> getConfigurationClasses() {
		List<Class<? extends KSConfiguration>> classes = new ArrayList<Class<? extends KSConfiguration>>();
		classes.add(AccountInfo.class);
		return classes;
	}

}
